package com.wthink.hzgj.util

import java.net.InetSocketAddress
import java.net.Socket
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

object TcpHelp {

    private val id2con = ConcurrentHashMap<Long, Tcp>()
    private val executor = Executors.newCachedThreadPool()
    private val empty = ByteArray(0)
    var socket = Socket()

    init {
        executor.execute {
            while (true) {
                val start = System.currentTimeMillis()
                try {
                    val iterator = id2con.entries.iterator()
                    while (iterator.hasNext()) {
                        val tcp = iterator.next().value
                        checkAlive(tcp)
                        connect(tcp)
                    }
                } catch (err: Throwable) {

                }

                val sleep = 10 * 1000 - (System.currentTimeMillis() - start)
                if (sleep > 0) {
                    TimeUnit.MILLISECONDS.sleep(sleep)
                }
            }
        }
    }

    private fun checkAlive(tcp: Tcp) {
        if (tcp.alive && tcp.disConnectTime > 0) {
            val now = System.currentTimeMillis()
            if (now - tcp.lastAliveAt > tcp.disConnectTime) {
                close(tcp)
            }
        }
    }

    private fun close(tcp: Tcp) {
        try {
            tcp.socket.close()
        } catch (err: Throwable) {
        }
        tcp.alive = false

        val client = Socket()
        client.setTcpNoDelay(true)
        client.setSoLinger(true, 0)
        client.setKeepAlive(true)
        tcp.socket = client
    }

    private fun connect(tcp: Tcp) {
        if (tcp.alive) {
            return
        }

        try {
            tcp.socket.connect(InetSocketAddress(tcp.host, tcp.port), tcp.connectTimeout)
            tcp.alive = true
            tcp.lastAliveAt = System.currentTimeMillis()
            tcp.onConnect()
            socket = tcp.socket
        } catch (err: Throwable) {
        }
    }

    fun connect(
        host: String,
        port: Int,
        onConnect: () -> Unit = {},
        connectTimeout: Int = 5000,
        disConnectTime: Int = 0
    ): Long {
        val id = System.currentTimeMillis()

        val client = Socket()
        client.setTcpNoDelay(true)
        client.setSoLinger(true, 0)
        client.setKeepAlive(true)
        id2con[id] = Tcp(host, port, client)
        return id
    }

    fun read(id: Long): ByteArray {
        val con = id2con.get(id)
        if (con == null || !con.alive) {
            return empty
        }

        val available = con.socket.getInputStream().available()
        if (available < 0) {
            close(con)
            return empty
        }

        if (available == 0) {
            return empty
        }

        val content = ByteArray(available)
        try {
            con.socket.getInputStream().read(content)
            con.lastAliveAt = System.currentTimeMillis()
            return content
        } catch (err: Throwable) {
            close(con)
            return empty
        }
    }

    fun send(id: Long, content: ByteArray): Boolean {
        val tcp = id2con.get(id)
        if (tcp == null || !tcp.alive) {
            return false
        }

        try {
            tcp.socket.getOutputStream().write(content)
            tcp.lastAliveAt = System.currentTimeMillis()
            return true
        } catch (err: Throwable) {
            close(tcp)
            return false
        }
    }

    fun isAlive(id: Long): Boolean {
        val tcp = id2con.get(id)
        return tcp != null && tcp.alive
    }

    fun reconnect(id: Long) {
        val tcp = id2con.get(id)
        if (tcp != null) {
            close(tcp)
        }
    }

    fun close(id: Long) {
        id2con.remove(id)?.socket?.close()
    }

    fun getClient():Socket{
        return socket
    }

    private class Tcp(
        val host: String,
        val port: Int,
        @Volatile
        var socket: Socket,
        val connectTimeout: Int = 5000,
        val onConnect: () -> Unit = {},
        @Volatile
        var alive: Boolean = false,
        @Volatile
        var lastAliveAt: Long = 0L,
        val disConnectTime: Int = 0
    )
}